package cn.stimd.spring.context.annotation;

import cn.stimd.spring.beans.factory.annotation.AnnotatedBeanDefinition;
import cn.stimd.spring.beans.factory.annotation.AnnotatedGenericBeanDefinition;
import cn.stimd.spring.beans.factory.config.BeanDefinition;
import cn.stimd.spring.beans.factory.support.BeanDefinitionRegistry;
import cn.stimd.spring.beans.factory.support.RootBeanDefinition;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.env.Environment;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.MethodMetadata;
import org.springframework.util.StringUtils;

import java.util.HashMap;
import java.util.Map;
import java.util.Set;

/**
 * Created by Stimd
 * 项目地址：https://gitee.com/stimd/spring-wheel
 * 公众号：Java编程探微
 */
public class ConfigurationClassBeanDefinitionReader {
    private final Log logger = LogFactory.getLog(getClass());
    private final BeanDefinitionRegistry registry;
    private final ConditionEvaluator conditionEvaluator;
    private final AnnotationBeanNameGenerator beanNameGenerator = new AnnotationBeanNameGenerator();
    private TrackedConditionEvaluator trackedConditionEvaluator = new TrackedConditionEvaluator();

    ConfigurationClassBeanDefinitionReader(BeanDefinitionRegistry registry, ResourceLoader resourceLoader, Environment environment) {
        this.registry = registry;
        this.conditionEvaluator = new ConditionEvaluator(registry, environment, resourceLoader);
    }


    //读取configurationModel，根据内容注册BeanDefinition
    public void loadBeanDefinitions(Set<ConfigurationClass> configurationModel) {
        for (ConfigurationClass configClass : configurationModel) {
            loadBeanDefinitionsForConfigurationClass(configClass);
        }
    }


    private void loadBeanDefinitionsForConfigurationClass(ConfigurationClass configClass) {
        //判断配置类是否应该被处理
        boolean skip = this.trackedConditionEvaluator.shouldSkip(configClass);
        if (skip) {
            //移除已注册的BeanDefinition，否则配置类仍会被实例化
            this.registry.removeBeanDefinition(configClass.getBeanName());
            return;
        }

        //1. 注册配置类，这里的导入是指通过@Import注解或内部类（引导配置类和通过扫描加载的配置类已经注册过了）
        if(configClass.isImported()){
            registerBeanDefinitionForImportedConfigurationClass(configClass);
        }

        //2. 注册BeanMethod
        for (BeanMethod beanMethod : configClass.getBeanMethods()) {
            loadBeanDefinitionsForBeanMethod(beanMethod);
        }

        //3. 注册导入的类
        loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
    }


    //将ConfigurationClass自身注册成一个BeanDefinition
    private void registerBeanDefinitionForImportedConfigurationClass(ConfigurationClass configClass) {
        AnnotationMetadata metadata = configClass.getMetadata();
        AnnotatedGenericBeanDefinition configBeanDef = new AnnotatedGenericBeanDefinition(metadata);
        AnnotationConfigUtils.processCommonDefinitionAnnotations(configBeanDef, metadata);
        String configBeanName = this.beanNameGenerator.generateBeanName(configBeanDef);
        configClass.setBeanName(configBeanName);
        this.registry.registerBeanDefinition(configBeanName, configBeanDef);
        logger.info("[Context] [配置类] --> 注册导入的配置类, beanName: " + configBeanName);
    }


    private void loadBeanDefinitionsForBeanMethod(BeanMethod beanMethod) {
        ConfigurationClass configClass = beanMethod.getConfigurationClass();
        MethodMetadata metadata = beanMethod.getMetadata();
        String methodName = metadata.getMethodName();

        //1. 根据Condition判断是否需要加载（REGISTER_BEAN表示指定匹配注册阶段的条件，比如@ConditionalOnBean）
        if (this.conditionEvaluator.shouldSkip(metadata)) {
            return;
        }

        //如果@Bean的value属性为空，使用方法名作为beanName
        AnnotationAttributes bean = AnnotationConfigUtils.attributesFor(metadata, Bean.class);
        String beanName = bean.getString("name");
        if(StringUtils.isEmpty(beanName)){
            beanName = methodName;
        }

        //2. 判断是否要覆盖可能已存在的BeanMethod对应的BeanDefinition
        if (isOverriddenByExistingDefinition(beanMethod, beanName)) {
            logger.info("[Context] [配置类] --> 单例[" + beanName + "]的BeanMethod[" +  beanMethod.getMetadata().getMethodName() + "]已存在");
            return;
        }

        //3. 为BeanMethod创建BeanDefinition，并指定factoryMethodName属性，表明这是一个工厂方法
        ConfigurationClassBeanDefinition beanDef = new ConfigurationClassBeanDefinition(configClass.getMetadata(), metadata);
        beanDef.setFactoryBeanName(configClass.getBeanName());  //设置FactoryBean名称，即外层的配置类
        beanDef.setUniqueFactoryMethodName(methodName);         //设置FactoryMethod名称，也就是@Bean方法名

        //4. 处理与@Bean相关的注解，比如@Role、@Primary等
        AnnotationConfigUtils.processCommonDefinitionAnnotations(beanDef, metadata);

        //5. 将BeanDefinition注册到容器中
        this.registry.registerBeanDefinition(beanName, beanDef);
        logger.info("[Context] [配置类] --> 注册BeanMethod, beanName: " + beanName);
    }


    /**
     * 判断是否要覆盖已存在的BeanDefinition
     * @param beanMethod 待注册的BeanMethod
     * @param beanName   BeanMethod对应的Bean的名称
     * @return 返回false说明可以覆盖
     */
    protected boolean isOverriddenByExistingDefinition(BeanMethod beanMethod, String beanName) {
        //1. 容器中不存在，直接返回先注册，否则需要与当前BeanMethod进行比较
        if (!this.registry.containsBeanDefinition(beanName)) {
            return false;
        }

        /* 2. BeanDefinition通过配置类创建的情况
         * 1) 不管是本类还是父类，如果两个BeanMethod的beanName相同，不覆盖，以最先加载的为准
         * 2) 如果A配置类和B配置类的BeanMethod的beanName相同，且A类先解析，那么B类会覆盖A类BeanMethod创建的BeanDefinition
         */
        BeanDefinition existingBeanDef = this.registry.getBeanDefinition(beanName);
        if (existingBeanDef instanceof ConfigurationClassBeanDefinition) {
            ConfigurationClassBeanDefinition ccbd = (ConfigurationClassBeanDefinition) existingBeanDef;
            return ccbd.getMetadata().getClassName().equals(
                    beanMethod.getConfigurationClass().getMetadata().getClassName());
        }

        //2. 已存在的BeanDefinition是通过扫描的方式加载的，但BeanMethod的优先级更高，允许覆盖
        if (existingBeanDef instanceof ScannedGenericBeanDefinition) {
            return false;
        }

        //3. 根据BeanDefinition的role属性来判断，允许应用程序的Bean覆盖框架生成的Bean
        if (existingBeanDef.getRole() > BeanDefinition.ROLE_APPLICATION) {
            return false;
        }
        //4. 其余情况，一律允许覆盖
        return true;
    }


    private void loadBeanDefinitionsFromRegistrars(Map<ImportBeanDefinitionRegistrar, AnnotationMetadata> registrars) {
        for (Map.Entry<ImportBeanDefinitionRegistrar, AnnotationMetadata> entry : registrars.entrySet()) {
            //key为ImportBeanDefinitionRegistrar接口的实现类，value为待注册组件的元数据
            entry.getKey().registerBeanDefinitions(entry.getValue(), this.registry);
        }
    }


    private static class ConfigurationClassBeanDefinition extends RootBeanDefinition implements AnnotatedBeanDefinition {
        private final AnnotationMetadata annotationMetadata;
        private final MethodMetadata factoryMethodMetadata;

        public ConfigurationClassBeanDefinition(AnnotationMetadata annotationMetadata, MethodMetadata beanMethodMetadata) {
            this.annotationMetadata = annotationMetadata;
            this.factoryMethodMetadata = beanMethodMetadata;
        }

        @Override
        public AnnotationMetadata getMetadata() {
            return this.annotationMetadata;
        }

        @Override
        public MethodMetadata getFactoryMethodMetadata() {
            return this.factoryMethodMetadata;
        }
    }


    private class TrackedConditionEvaluator {
        private final Map<ConfigurationClass, Boolean> skipped = new HashMap<>();

        public boolean shouldSkip(ConfigurationClass configClass) {
            Boolean skip = this.skipped.get(configClass);
            if (skip == null) {
                //检查配置类是否由宿主导入的
                if (configClass.isImported()) {
                    boolean allSkipped = true;
                    for (ConfigurationClass importedBy : configClass.getImportedBy()) {
                        if (!shouldSkip(importedBy)) {
                            allSkipped = false;
                            break;
                        }
                    }
                    //如果所有的宿主都不满足条件，说明当前配置类不应被加载
                    if (allSkipped) {
                        skip = true;
                    }
                }

                //对配置类本身进行判定（配置类不是被导入的，或者至少有一个宿主类满足条件）
                if (skip == null) {
                    skip = conditionEvaluator.shouldSkip(configClass.getMetadata());
                }
                this.skipped.put(configClass, skip);
            }
            return skip;
        }
    }
}
